﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Security.Principal;
using System.Text;
using FinWeb3.Models.Repository;

namespace FinWeb3.Models
{
    public class StatementManager
    {
        private IRepository<Statement> repository;
        private IRepository<Bank> bankRepository;
        private IIdentity user;


        public StatementManager(IRepository<Statement> repository, IRepository<Bank> bankRepository, IIdentity user)
        {
            this.repository = repository;
            this.bankRepository = bankRepository;
            this.user = user;
        }

        public decimal GetBalance(int bankId)
        {
            return GetBalance(bankId, DateTime.Today);
        }

        public decimal GetBalance(int bankId, DateTime lastDay)
        {
            
            var total = 
                repository
                .GetAll(user)
                .Where(x => x.Day <= lastDay && x.BankId == bankId)
                .Sum(x => x.Value);

            if (total == null)
                return 0M;
            else
                return total;
        }

        public List<Statement> Get(DateTime fromDate, DateTime toDate)
        {
            return 
                repository.GetAll(user)
                .Where(x => x.Day >= fromDate && x.Day <= toDate)
                .OrderByDescending(x => x.Value)
                .OrderBy(x => x.Day)
                .ToList();
        }

        public List<Statement> Get(int bankId, DateTime fromDate, DateTime toDate)
        {
            return 
                repository.GetAll(user)
                .Where(x => x.Day >= fromDate && x.Day <= toDate && x.BankId == bankId)
                .OrderByDescending(x => x.Value)
                .OrderBy(x => x.Day)
                .ToList();
        }

        public int Save(StreamReader statementStream)
        {
            DataSet ds = CriarDatasetDoExtrato(statementStream);
            return SaveStatementLinesFromDataSet(ds, user);
        }

        public void AdjustStatementTotal(int bankId, decimal realValue)
        {
            var bankManager = new BankManager(bankRepository, user);

            decimal valueNow = GetBalance(bankId);
            DateTime firstDate = repository.GetAll(user).Min(x => x.Day).AddDays(-1);
            var bank = bankManager.Get(bankId);

            if (bank != null)
            {
                Statement stm = new Statement()
                {
                    BankId = bank.Id,
                    Day = firstDate,
                    Description = "Ajuste de saldo feito em " + DateTime.Today.ToString("dd/MM/yyyy"),
                    FitId = firstDate.ToString("yyyyMMdd") + "099",
                    UserName = user.Name,
                    Value = realValue - valueNow
                };

                repository.Save(stm, user);
            }
        }


        #region Diogenes implementation!

        private string lineData;

        private bool LineAlreadyRegistered(Statement statement)
        {
            var statements = repository.GetAll(user).Where(x =>
                x.BankId == statement.BankId &&
                x.Day == statement.Day &&
                x.Description == statement.Description).ToList();

            int count = statements.Count(x => x.Value == statement.Value);

            return count != 0;
        }

        private int SaveStatementLinesFromDataSet(DataSet ds, IIdentity user)
        {
            var bankManager = new BankManager(bankRepository, user);
            DataTable tabela = ds.Tables[0];
            int count = 0;

            if (tabela.Rows.Count == 0)
                return 0;

            var statementBankId = Convert.ToString(tabela.Rows[0]["BANKID"]);
            var statementAccountId = Convert.ToString(tabela.Rows[0]["ACCTID"]);

            var bank = bankManager.Get(statementBankId, statementAccountId);

            if (bank == null)
            {
                bank = new Bank()
                {
                    AccountId = statementAccountId,
                    BankId = statementBankId,
                    Name = "(sem nome)",
                    UserName = user.Name
                };

                bankManager.Save(bank);
            }

            for (int i = 0; i < tabela.Rows.Count; i++)
            {
                DataRow dr = tabela.Rows[i];
                Statement linha = new Statement();

                linha.BankId = bank.Id;

                if (tabela.Columns.Contains("NAME"))
                    linha.Description = Convert.ToString(dr["NAME"]);
                else
                    linha.Description = Convert.ToString(dr["MEMO"]);

                linha.Day = ConverterData(dr["DTPOSTED"]);
                linha.FitId = Convert.ToString(dr["FITID"]);
                linha.Value = Convert.ToDecimal(dr["TRNAMT"], new CultureInfo("en-US"));
                linha.UserName = user.Name;

                if (!this.LineAlreadyRegistered(linha) && !this.UselessLine(linha))
                {
                    linha.Save();
                    count++;
                }
            }

            return count;
        }

        private bool UselessLine(Statement line)
        {
            return line.Description.Contains("SALDO ANTERIOR") ||
                line.Description.Contains("RECURSOS EM C-C") ||
                line.Description.Contains("SALDO FINAL");
        }

        private DateTime ConverterData(object data)
        {
            string dataOfx = data.ToString();
            int ano = Convert.ToInt32(dataOfx.Substring(0, 4));
            int mes = Convert.ToInt32(dataOfx.Substring(4, 2));
            int dia = Convert.ToInt32(dataOfx.Substring(6, 2));

            return new DateTime(ano, mes, dia);
        }

        private DataSet CriarDatasetDoExtrato(StreamReader reader)
        {
            DataSet ds = new DataSet();
            StringBuilder generatedXml = new StringBuilder();
            StringReader xmlSR;

            while (!reader.EndOfStream)
            {
                string line = reader.ReadLine();

                if (line.IndexOf("<") != -1 || line.IndexOf(">") != -1)
                {
                    generatedXml.Append(this.AchaTag(line));
                }
            }

            xmlSR = new System.IO.StringReader(generatedXml.ToString());

            ds.ReadXml(xmlSR);

            return ds;
        }

        /// <summary>
        /// Looks for specific tags on the ofx file
        /// </summary>
        /// <param name="strTag">Tag to look</param>
        /// <returns>Tag fixed to correct xml syntax </returns>
        private string AchaTag(string strTag)
        {
            string procura;
            string fecha;
            string info;
            string[] vet;
            string[] vetFecha;
            string strTemp;
            int tagFim;

            info = "<DTSERVER>;<BANKID>;<ACCTID>;<DTSTART>;<DTEND>;";
            procura = "<TRNTYPE>;<DTPOSTED>;<TRNAMT>;<CHECKNUM>;<MEMO>;<TRNTYPE>;<DTPOSTED>;<TRNAMT>;<FITID>;<CHECKNUM>;<NAME>;";
            fecha = "</TRNTYPE>;</DTPOSTED>;</TRNAMT>;</CHECKNUM>;</MEMO>;</TRNTYPE>;</DTPOSTED>;</TRNAMT>;</FITID>;</CHECKNUM>;</NAME>;";

            vet = procura.Split(';');
            vetFecha = fecha.Split(';');

            strTemp = this.SeparaTag(strTag);

            if (strTag.Length > 0)
            {
                if (procura.IndexOf(strTemp) != -1)
                {
                    tagFim = this.ProcuraVetor(vet, strTemp);

                    //replaces invalid xml caracters on the ofx files
                    strTag = strTag.Replace("&", "-");
                    strTag = strTag.Replace("/", "-");

                    return strTag + vetFecha[tagFim] + "\n";
                }

                else if (info.IndexOf(strTemp) != -1)
                {
                    strTag = strTag.Replace("&", "-");
                    strTag = strTag.Replace("/", "-");

                    //if (strTemp == "<ACCTID>")
                    //{
                    //    if (strTag != "<ACCTID>" + System.Text.RegularExpressions.Regex.Replace(bank.AccountId, "[^1234567890]", ""))
                    //    {
                    //        throw new Exception("Você está importando um extrato que não é dessa conta!");
                    //    }
                    //}

                    lineData += "\n" + strTag + "</" + strTemp.Substring(1);
                }
                else
                {
                    switch (strTag.ToUpper())
                    {
                        case "<BANKTRANLIST>":
                        case "</BANKTRANLIST>":
                        case "</STMTTRN>":
                            return strTag + "\n";
                            break;

                        case "<STMTTRN>":
                            return strTag + lineData + "\n";
                            break;
                    }
                }
            }

            return "";
        }

        private string SeparaTag(string tag)
        {
            int pos_ini, pos_fim;
            pos_ini = tag.IndexOf("<");
            pos_fim = tag.IndexOf(">");
            pos_fim = pos_fim - pos_ini;

            return tag.Substring(pos_ini, pos_fim + 1);
        }

        /// <summary>
        /// Looks for a string inside a string array
        /// </summary>
        /// <param name="vet">Array to look</param>
        /// <param name="procura">The string we are looking for</param>
        /// <returns>Position of the string inside the array</returns>
        private int ProcuraVetor(string[] vet, string procura)
        {
            string value;
            ;

            for (int i = 0; i < vet.Length; i++)
            {
                value = vet[i];

                if (value.ToUpper() == procura.ToUpper())
                    return i;
            }

            return 0;
        }

        #endregion        
    }
}
